Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 63189f4ed082c50c6ce40135b97c2410791dd131


Parents : 76d223e
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-07-09T08:39:32-05:00

feat(Reticulum): implement shared-instance and RPC settings management, including API endpoints for retrieving and updating configurations.

Changes
Diff

diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py
index cbc54245..cc51db2f 100644
--- a/meshchatx/meshchat.py
+++ b/meshchatx/meshchat.py
@@ -3359,6 +3359,118 @@ class ReticulumMeshChat:
return reticulum_config
+ @staticmethod
+ def _parse_rns_config_bool(value, default=False):
+ """Parse Reticulum config Yes/No / bool-ish values."""
+ if value is None:
+ return bool(default)
+ if isinstance(value, bool):
+ return value
+ if isinstance(value, (int, float)):
+ return value != 0
+ text = str(value).strip().lower()
+ if text in ("yes", "true", "1", "on"):
+ return True
+ if text in ("no", "false", "0", "off", ""):
+ return False
+ return bool(default)
+
+ @staticmethod
+ def _format_rns_config_bool(value):
+ return "Yes" if bool(value) else "No"
+
+ def _get_reticulum_rpc_key_hex(self):
+ """Return the live or configured RPC key as lowercase hex, or None."""
+ reticulum = getattr(self, "reticulum", None)
+ if reticulum is not None:
+ key = getattr(reticulum, "rpc_key", None)
+ if isinstance(key, (bytes, bytearray)) and key:
+ try:
+ return RNS.hexrep(key, delimit=False)
+ except Exception:
+ return bytes(key).hex()
+ if isinstance(key, str) and key.strip():
+ return key.strip().lower()
+ section = self._get_reticulum_section()
+ raw = section.get("rpc_key")
+ if isinstance(raw, str) and raw.strip():
+ return raw.strip().lower()
+ return None
+
+ def _build_reticulum_instance_settings(self):
+ """Sideband-parity shared-instance / RPC / hop-obfuscation settings."""
+ section = self._get_reticulum_section()
+ reticulum = getattr(self, "reticulum", None)
+ share_default = True
+ if reticulum is not None and hasattr(reticulum, "share_instance"):
+ share_default = bool(reticulum.share_instance)
+ share_instance = self._parse_rns_config_bool(
+ section.get("share_instance"),
+ default=share_default,
+ )
+ if "local_hops_delta" in section:
+ local_hops_delta = self._parse_rns_config_bool(
+ section.get("local_hops_delta"),
+ default=False,
+ )
+ else:
+ local_hops_delta = False
+ if reticulum is not None and hasattr(RNS.Reticulum, "local_hops_delta"):
+ try:
+ local_hops_delta = bool(RNS.Reticulum.local_hops_delta())
+ except Exception:
+ pass
+
+ shared_type_raw = section.get("shared_instance_type")
+ shared_instance_type = None
+ if isinstance(shared_type_raw, str) and shared_type_raw.strip():
+ shared_instance_type = shared_type_raw.strip().lower()
+ elif reticulum is not None:
+ live_type = getattr(reticulum, "shared_instance_type", None)
+ if isinstance(live_type, str) and live_type.strip():
+ shared_instance_type = live_type.strip().lower()
+
+ instance_name = section.get("instance_name")
+ if not isinstance(instance_name, str) or not instance_name.strip():
+ instance_name = "default"
+ else:
+ instance_name = instance_name.strip()
+
+ is_connected = bool(
+ reticulum is not None
+ and getattr(reticulum, "is_connected_to_shared_instance", False),
+ )
+ rpc_key = self._get_reticulum_rpc_key_hex()
+ rpc_snippet = None
+ if rpc_key:
+ type_line = shared_instance_type or "tcp"
+ rpc_snippet = f"shared_instance_type = {type_line}\nrpc_key = {rpc_key}"
+
+ return {
+ "share_instance": share_instance,
+ "local_hops_delta": local_hops_delta,
+ "shared_instance_type": shared_instance_type,
+ "instance_name": instance_name,
+ "rpc_key": rpc_key,
+ "rpc_config_snippet": rpc_snippet,
+ "is_connected_to_shared_instance": is_connected,
+ "enable_transport": self._parse_rns_config_bool(
+ section.get("enable_transport"),
+ default=bool(
+ reticulum is not None
+ and getattr(reticulum, "transport_enabled", lambda: False)(),
+ ),
+ ),
+ "respond_to_probes": self._parse_rns_config_bool(
+ section.get("respond_to_probes"),
+ default=False,
+ ),
+ "enable_remote_management": self._parse_rns_config_bool(
+ section.get("enable_remote_management"),
+ default=False,
+ ),
+ }
+
def _get_interfaces_section(self):
try:
if hasattr(self, "reticulum") and self.reticulum:
@@ -8181,6 +8293,105 @@ class ReticulumMeshChat:
},
)
+ @routes.get("/api/v1/reticulum/instance")
+ async def reticulum_instance_get(request):
+ """Shared-instance, RPC, and hop-obfuscation settings (Sideband parity)."""
+ return web.json_response(
+ {"instance": self._build_reticulum_instance_settings()},
+ )
+
+ @routes.patch("/api/v1/reticulum/instance")
+ async def reticulum_instance_patch(request):
+ """Update [reticulum] shared-instance / hop-obfuscation options and reload."""
+ try:
+ data = await request.json()
+ except Exception:
+ return web.json_response(
+ {"message": "Invalid request body"},
+ status=400,
+ )
+ if not isinstance(data, dict):
+ return web.json_response(
+ {"message": "Invalid request body"},
+ status=400,
+ )
+
+ reticulum_config = self._get_reticulum_section()
+ changed = False
+
+ bool_keys = (
+ "share_instance",
+ "local_hops_delta",
+ "respond_to_probes",
+ "enable_remote_management",
+ )
+ for key in bool_keys:
+ if key not in data:
+ continue
+ reticulum_config[key] = self._format_rns_config_bool(
+ self._parse_rns_config_bool(data.get(key), default=False),
+ )
+ changed = True
+
+ if "instance_name" in data:
+ name = data.get("instance_name")
+ if name is None or str(name).strip() == "":
+ reticulum_config.pop("instance_name", None)
+ else:
+ cleaned = str(name).strip()
+ if len(cleaned) > 64 or any(c.isspace() for c in cleaned):
+ return web.json_response(
+ {
+ "message": "instance_name must be 1-64 characters without whitespace",
+ },
+ status=400,
+ )
+ reticulum_config["instance_name"] = cleaned
+ changed = True
+
+ if "shared_instance_type" in data:
+ raw_type = data.get("shared_instance_type")
+ if raw_type is None or str(raw_type).strip() == "":
+ reticulum_config.pop("shared_instance_type", None)
+ else:
+ cleaned_type = str(raw_type).strip().lower()
+ if cleaned_type not in ("tcp", "unix"):
+ return web.json_response(
+ {
+ "message": "shared_instance_type must be 'tcp' or 'unix'",
+ },
+ status=400,
+ )
+ reticulum_config["shared_instance_type"] = cleaned_type
+ changed = True
+
+ if not changed:
+ return web.json_response(
+ {"instance": self._build_reticulum_instance_settings()},
+ )
+
+ if not self._write_reticulum_config():
+ return web.json_response(
+ {"message": "Failed to write Reticulum config"},
+ status=500,
+ )
+
+ if not await self.reload_reticulum():
+ return web.json_response(
+ {
+ "message": "Instance settings were saved, but RNS reload failed.",
+ "instance": self._build_reticulum_instance_settings(),
+ },
+ status=500,
+ )
+
+ return web.json_response(
+ {
+ "message": "Reticulum instance settings updated and RNS restarted.",
+ "instance": self._build_reticulum_instance_settings(),
+ },
+ )
+
@routes.post("/api/v1/reticulum/reload")
async def reticulum_reload(request):
success = await self.reload_reticulum()

diff --git a/meshchatx/src/frontend/components/settings/SettingsPage.vue b/meshchatx/src/frontend/components/settings/SettingsPage.vue
index d513f976..dca03f1f 100644
--- a/meshchatx/src/frontend/components/settings/SettingsPage.vue
+++ b/meshchatx/src/frontend/components/settings/SettingsPage.vue
@@ -1936,7 +1936,7 @@
</div>
</section>
- <!-- Transport -->
+ <!-- Transport / shared instance / hop obfuscation -->
<section v-show="showSection('transport')" class="settings-section break-inside-avoid">
<header class="settings-section__header">
<div>
@@ -1959,6 +1959,138 @@
}}</span>
</span>
</label>
+
+ <label class="setting-toggle">
+ <Toggle
+ id="share-reticulum-instance"
+ v-model="reticulumInstance.share_instance"
+ :disabled="reticulumInstanceSaving"
+ @update:model-value="onShareInstanceChange"
+ />
+ <span class="setting-toggle__label">
+ <span class="setting-toggle__title">{{
+ $t("app.share_reticulum_instance")
+ }}</span>
+ <span class="setting-toggle__description">{{
+ $t("app.share_reticulum_instance_description")
+ }}</span>
+ </span>
+ </label>
+
+ <label class="setting-toggle">
+ <Toggle
+ id="obfuscate-hops"
+ v-model="reticulumInstance.local_hops_delta"
+ :disabled="reticulumInstanceSaving"
+ @update:model-value="onLocalHopsDeltaChange"
+ />
+ <span class="setting-toggle__label">
+ <span class="setting-toggle__title">{{ $t("app.obfuscate_hops") }}</span>
+ <span class="setting-toggle__description">{{
+ $t("app.obfuscate_hops_description")
+ }}</span>
+ </span>
+ </label>
+
+ <label class="setting-toggle">
+ <Toggle
+ id="respond-to-probes"
+ v-model="reticulumInstance.respond_to_probes"
+ :disabled="reticulumInstanceSaving"
+ @update:model-value="onRespondToProbesChange"
+ />
+ <span class="setting-toggle__label">
+ <span class="setting-toggle__title">{{ $t("app.respond_to_probes") }}</span>
+ <span class="setting-toggle__description">{{
+ $t("app.respond_to_probes_description")
+ }}</span>
+ </span>
+ </label>
+
+ <label class="setting-toggle">
+ <Toggle
+ id="enable-remote-management"
+ v-model="reticulumInstance.enable_remote_management"
+ :disabled="reticulumInstanceSaving"
+ @update:model-value="onEnableRemoteManagementChange"
+ />
+ <span class="setting-toggle__label">
+ <span class="setting-toggle__title">{{
+ $t("app.enable_remote_management")
+ }}</span>
+ <span class="setting-toggle__description">{{
+ $t("app.enable_remote_management_description")
+ }}</span>
+ </span>
+ </label>
+
+ <div class="grid gap-3 sm:grid-cols-2">
+ <label class="block space-y-1">
+ <span class="text-sm font-medium text-gray-800 dark:text-zinc-200">{{
+ $t("app.shared_instance_type")
+ }}</span>
+ <select
+ v-model="reticulumInstance.shared_instance_type"
+ class="w-full rounded-xl border border-gray-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-gray-900 dark:text-white"
+ :disabled="reticulumInstanceSaving"
+ @change="onSharedInstanceTypeChange"
+ >
+ <option value="">{{ $t("app.shared_instance_type_default") }}</option>
+ <option value="unix">unix</option>
+ <option value="tcp">tcp</option>
+ </select>
+ <span class="text-xs text-gray-500 dark:text-zinc-400">{{
+ $t("app.shared_instance_type_description")
+ }}</span>
+ </label>
+ <label class="block space-y-1">
+ <span class="text-sm font-medium text-gray-800 dark:text-zinc-200">{{
+ $t("app.instance_name")
+ }}</span>
+ <input
+ v-model="reticulumInstance.instance_name"
+ type="text"
+ maxlength="64"
+ class="w-full rounded-xl border border-gray-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-gray-900 dark:text-white"
+ :disabled="reticulumInstanceSaving"
+ @change="onInstanceNameChange"
+ />
+ <span class="text-xs text-gray-500 dark:text-zinc-400">{{
+ $t("app.instance_name_description")
+ }}</span>
+ </label>
+ </div>
+
+ <div
+ class="rounded-xl border border-gray-200 dark:border-zinc-700 bg-black/2 dark:bg-white/2 p-3 space-y-2"
+ >
+ <div class="text-sm font-medium text-gray-900 dark:text-zinc-100">
+ {{ $t("app.rpc_config") }}
+ </div>
+ <p class="text-xs text-gray-600 dark:text-zinc-400">
+ {{ $t("app.rpc_config_description") }}
+ </p>
+ <p
+ v-if="reticulumInstance.is_connected_to_shared_instance"
+ class="text-xs text-amber-700 dark:text-amber-300"
+ >
+ {{ $t("app.connected_to_shared_instance") }}
+ </p>
+ <pre
+ class="text-xs font-mono whitespace-pre-wrap break-all text-gray-800 dark:text-zinc-200 bg-white/60 dark:bg-zinc-900/60 rounded-lg p-2 border border-gray-200/70 dark:border-zinc-800"
+ >{{
+ reticulumInstance.rpc_config_snippet || $t("app.rpc_config_unavailable")
+ }}</pre>
+ <button
+ type="button"
+ class="inline-flex items-center gap-2 rounded-xl bg-blue-600 hover:bg-blue-700 disabled:opacity-50 text-white text-sm font-semibold px-3 py-2"
+ :disabled="!reticulumInstance.rpc_config_snippet"
+ @click="copyRpcConfigSnippet"
+ >
+ <MaterialDesignIcon icon-name="content-copy" class="w-4 h-4" />
+ {{ $t("app.copy_rpc_config") }}
+ </button>
+ </div>
</div>
</section>
@@ -2908,7 +3040,11 @@ import {
fetchMergedConfig,
patchServerConfig,
} from "../../js/settings/settingsConfigService";
-import { applyTransportMode } from "../../js/settings/settingsTransportService";
+import {
+ applyTransportMode,
+ applyReticulumInstanceSettings,
+ fetchReticulumInstanceSettings,
+} from "../../js/settings/settingsTransportService";
import * as maintenanceClient from "../../js/settings/settingsMaintenanceClient";
import {
loadVisualiserDisplayPrefs,
@@ -3067,6 +3203,19 @@ export default {
closeBehavior: "ask",
trayEnabled: true,
},
+ reticulumInstance: {
+ share_instance: true,
+ local_hops_delta: false,
+ respond_to_probes: false,
+ enable_remote_management: false,
+ shared_instance_type: "",
+ instance_name: "default",
+ rpc_key: null,
+ rpc_config_snippet: null,
+ is_connected_to_shared_instance: false,
+ enable_transport: false,
+ },
+ reticulumInstanceSaving: false,
};
},
computed: {
@@ -3195,8 +3344,79 @@ export default {
this.loadGifCount();
this.loadVisualiserDisplayPrefsFromStorage();
this.loadDesktopCloseSettings();
+ this.loadReticulumInstanceSettings();
},
methods: {
+ async loadReticulumInstanceSettings() {
+ try {
+ const instance = await fetchReticulumInstanceSettings(window.api);
+ if (instance && typeof instance === "object") {
+ this.reticulumInstance = {
+ ...this.reticulumInstance,
+ ...instance,
+ shared_instance_type: instance.shared_instance_type || "",
+ instance_name: instance.instance_name || "default",
+ };
+ }
+ } catch (e) {
+ console.log(e);
+ }
+ },
+ async patchReticulumInstance(patch) {
+ if (this.reticulumInstanceSaving) return;
+ this.reticulumInstanceSaving = true;
+ try {
+ const response = await applyReticulumInstanceSettings(patch, window.api);
+ if (response?.data?.instance) {
+ const instance = response.data.instance;
+ this.reticulumInstance = {
+ ...this.reticulumInstance,
+ ...instance,
+ shared_instance_type: instance.shared_instance_type || "",
+ instance_name: instance.instance_name || "default",
+ };
+ }
+ if (response?.data?.message) {
+ ToastUtils.success(response.data.message);
+ }
+ } catch {
+ ToastUtils.error(this.$t("settings.failed_update_reticulum_instance"));
+ await this.loadReticulumInstanceSettings();
+ } finally {
+ this.reticulumInstanceSaving = false;
+ }
+ },
+ onShareInstanceChange(value) {
+ this.patchReticulumInstance({ share_instance: !!value });
+ },
+ onLocalHopsDeltaChange(value) {
+ this.patchReticulumInstance({ local_hops_delta: !!value });
+ },
+ onRespondToProbesChange(value) {
+ this.patchReticulumInstance({ respond_to_probes: !!value });
+ },
+ onEnableRemoteManagementChange(value) {
+ this.patchReticulumInstance({ enable_remote_management: !!value });
+ },
+ onSharedInstanceTypeChange() {
+ const value = this.reticulumInstance.shared_instance_type || null;
+ this.patchReticulumInstance({ shared_instance_type: value });
+ },
+ onInstanceNameChange() {
+ this.patchReticulumInstance({
+ instance_name: this.reticulumInstance.instance_name || "default",
+ });
+ },
+ async copyRpcConfigSnippet() {
+ const snippet = this.reticulumInstance.rpc_config_snippet;
+ if (!snippet) return;
+ try {
+ await navigator.clipboard.writeText(snippet);
+ ToastUtils.success(this.$t("app.rpc_config_copied"));
+ } catch {
+ ToastUtils.error(this.$t("app.copy_failed"));
+ }
+ },
async loadDesktopCloseSettings() {
if (!ElectronUtils.isElectron()) {
return;

diff --git a/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js b/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
index 6f1cd342..3c2aee6b 100644
--- a/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
+++ b/meshchatx/src/frontend/js/registries/coreSettingsSectionKeywords.js
@@ -239,6 +239,22 @@ export const CORE_SETTINGS_SECTION_KEYWORDS = {
"app.transport_description",
"app.enable_transport_mode",
"app.transport_toggle_description",
+ "app.share_reticulum_instance",
+ "app.share_reticulum_instance_description",
+ "app.obfuscate_hops",
+ "app.obfuscate_hops_description",
+ "app.respond_to_probes",
+ "app.respond_to_probes_description",
+ "app.enable_remote_management",
+ "app.enable_remote_management_description",
+ "app.shared_instance_type",
+ "app.instance_name",
+ "app.copy_rpc_config",
+ "app.rpc_config",
+ "share instance",
+ "rpc",
+ "hops",
+ "obfuscate",
],
interfaces: ["Adapters", "app.interfaces", "app.show_community_interfaces", "app.community_interfaces_description"],
blocked: ["Privacy", "Banished", "Manage Banished users and nodes"],

diff --git a/meshchatx/src/frontend/js/settings/settingsReticulumInstanceService.js b/meshchatx/src/frontend/js/settings/settingsReticulumInstanceService.js
new file mode 100644
index 00000000..c35e8692
--- /dev/null
+++ b/meshchatx/src/frontend/js/settings/settingsReticulumInstanceService.js
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: 0BSD
+
+/**
+ * Reticulum shared-instance / RPC / hop-obfuscation settings (Sideband parity).
+ *
+ * @param {{ get: (path: string) => Promise<{ data?: { instance?: object } }> }} api
+ * @returns {Promise<object>}
+ */
+export async function fetchReticulumInstanceSettings(api) {
+ const response = await api.get("/api/v1/reticulum/instance");
+ return response?.data?.instance ?? {};
+}
+
+/**
+ * @param {Record<string, unknown>} patch
+ * @param {{ patch: (path: string, body: object) => Promise<{ data?: { instance?: object, message?: string } }> }} api
+ * @returns {Promise<{ data?: { instance?: object, message?: string } }>}
+ */
+export async function applyReticulumInstanceSettings(patch, api) {
+ return api.patch("/api/v1/reticulum/instance", patch);
+}

diff --git a/meshchatx/src/frontend/js/settings/settingsTransportService.js b/meshchatx/src/frontend/js/settings/settingsTransportService.js
index 2175920b..80e90e7e 100644
--- a/meshchatx/src/frontend/js/settings/settingsTransportService.js
+++ b/meshchatx/src/frontend/js/settings/settingsTransportService.js
@@ -10,3 +10,5 @@ export async function applyTransportMode(enabled, api) {
}
return api.post("/api/v1/reticulum/disable-transport");
}
+
+export { applyReticulumInstanceSettings, fetchReticulumInstanceSettings } from "./settingsReticulumInstanceService.js";

diff --git a/meshchatx/src/frontend/locales/de.json b/meshchatx/src/frontend/locales/de.json
index 9ebc22d3..c1f8584e 100644
--- a/meshchatx/src/frontend/locales/de.json
+++ b/meshchatx/src/frontend/locales/de.json
@@ -431,7 +431,27 @@
"desktop_close_behavior_ask": "Ask every time",
"desktop_close_behavior_quit": "Quit application",
"desktop_close_behavior_background": "Keep running in background",
- "desktop_close_behavior_background_no_tray": "Minimize to taskbar"
+ "desktop_close_behavior_background_no_tray": "Minimize to taskbar",
+ "share_reticulum_instance": "Share Reticulum Instance",
+ "share_reticulum_instance_description": "Make this MeshChatX Reticulum instance available to other local programs (Sideband-style shared instance). Other apps can use connectivity without Transport enabled.",
+ "obfuscate_hops": "Obfuscate Hops",
+ "obfuscate_hops_description": "When enabled, Reticulum applies a random local hop delta (RNS local_hops_delta) so outbound traffic does not advertise a true zero-hop origin to the wider mesh.",
+ "respond_to_probes": "Respond to Probes",
+ "respond_to_probes_description": "Allow this instance to answer Reticulum probe requests from the network.",
+ "enable_remote_management": "Enable Remote Management",
+ "enable_remote_management_description": "Allow remote management of this Reticulum instance when configured in the RNS config.",
+ "shared_instance_type": "Shared Instance Type",
+ "shared_instance_type_default": "Platform default",
+ "shared_instance_type_description": "Use TCP when domain sockets are unavailable (common on Android). Leave default otherwise.",
+ "instance_name": "Instance Name",
+ "instance_name_description": "Isolate multiple shared instances on one system. Default is usually fine.",
+ "rpc_config": "RPC Access",
+ "rpc_config_description": "Copy these lines into another program's [reticulum] section for full shared-instance RPC access (management, interface status, path info).",
+ "rpc_config_unavailable": "RPC key is not available yet. Start Reticulum as a shared instance first.",
+ "rpc_config_copied": "RPC config copied to clipboard",
+ "copy_rpc_config": "Copy RPC Config",
+ "connected_to_shared_instance": "This MeshChatX process is attached to an external shared Reticulum instance. Edit that instance's config to change share/RPC settings.",
+ "copy_failed": "Failed to copy to clipboard"
},
"common": {
"open": "Öffnen",
@@ -2976,7 +2996,8 @@
"privacy_desc": "Data, access, and security",
"maintenance": "Maintenance",
"maintenance_desc": "Cleanup, export, and import"
- }
+ },
+ "failed_update_reticulum_instance": "Failed to update Reticulum instance settings!"
},
"debug": {
"title": "Debug Logs",

diff --git a/meshchatx/src/frontend/locales/en.json b/meshchatx/src/frontend/locales/en.json
index 78a821d9..51f7b385 100644
--- a/meshchatx/src/frontend/locales/en.json
+++ b/meshchatx/src/frontend/locales/en.json
@@ -153,6 +153,26 @@
"transport_description": "Relay paths and traffic for nearby peers.",
"enable_transport_mode": "Enable Transport Mode",
"transport_toggle_description": "Route announces, respond to path requests and help your mesh stay online.",
+ "share_reticulum_instance": "Share Reticulum Instance",
+ "share_reticulum_instance_description": "Make this MeshChatX Reticulum instance available to other local programs (Sideband-style shared instance). Other apps can use connectivity without Transport enabled.",
+ "obfuscate_hops": "Obfuscate Hops",
+ "obfuscate_hops_description": "When enabled, Reticulum applies a random local hop delta (RNS local_hops_delta) so outbound traffic does not advertise a true zero-hop origin to the wider mesh.",
+ "respond_to_probes": "Respond to Probes",
+ "respond_to_probes_description": "Allow this instance to answer Reticulum probe requests from the network.",
+ "enable_remote_management": "Enable Remote Management",
+ "enable_remote_management_description": "Allow remote management of this Reticulum instance when configured in the RNS config.",
+ "shared_instance_type": "Shared Instance Type",
+ "shared_instance_type_default": "Platform default",
+ "shared_instance_type_description": "Use TCP when domain sockets are unavailable (common on Android). Leave default otherwise.",
+ "instance_name": "Instance Name",
+ "instance_name_description": "Isolate multiple shared instances on one system. Default is usually fine.",
+ "rpc_config": "RPC Access",
+ "rpc_config_description": "Copy these lines into another program's [reticulum] section for full shared-instance RPC access (management, interface status, path info).",
+ "rpc_config_unavailable": "RPC key is not available yet. Start Reticulum as a shared instance first.",
+ "rpc_config_copied": "RPC config copied to clipboard",
+ "copy_rpc_config": "Copy RPC Config",
+ "connected_to_shared_instance": "This MeshChatX process is attached to an external shared Reticulum instance. Edit that instance's config to change share/RPC settings.",
+ "copy_failed": "Failed to copy to clipboard",
"requires_restart": "Requires restart after toggling.",
"show_community_interfaces": "Show Community Interfaces",
"community_interfaces_description": "Show community-maintained presets when adding new interfaces.",
@@ -1559,6 +1579,7 @@
"archived_pages_flushed": "Archived pages flushed.",
"failed_enable_transport": "Failed to enable transport mode!",
"failed_disable_transport": "Failed to disable transport mode!",
+ "failed_update_reticulum_instance": "Failed to update Reticulum instance settings!",
"failed_reload_reticulum": "Failed to reload Reticulum!",
"folders_exported": "Folders exported",
"failed_export_folders": "Failed to export folders",

diff --git a/meshchatx/src/frontend/locales/es.json b/meshchatx/src/frontend/locales/es.json
index 2a3d3648..096b1fc7 100644
--- a/meshchatx/src/frontend/locales/es.json
+++ b/meshchatx/src/frontend/locales/es.json
@@ -431,7 +431,27 @@
"desktop_close_behavior_ask": "Ask every time",
"desktop_close_behavior_quit": "Quit application",
"desktop_close_behavior_background": "Keep running in background",
- "desktop_close_behavior_background_no_tray": "Minimize to taskbar"
+ "desktop_close_behavior_background_no_tray": "Minimize to taskbar",
+ "share_reticulum_instance": "Share Reticulum Instance",
+ "share_reticulum_instance_description": "Make this MeshChatX Reticulum instance available to other local programs (Sideband-style shared instance). Other apps can use connectivity without Transport enabled.",
+ "obfuscate_hops": "Obfuscate Hops",
+ "obfuscate_hops_description": "When enabled, Reticulum applies a random local hop delta (RNS local_hops_delta) so outbound traffic does not advertise a true zero-hop origin to the wider mesh.",
+ "respond_to_probes": "Respond to Probes",
+ "respond_to_probes_description": "Allow this instance to answer Reticulum probe requests from the network.",
+ "enable_remote_management": "Enable Remote Management",
+ "enable_remote_management_description": "Allow remote management of this Reticulum instance when configured in the RNS config.",
+ "shared_instance_type": "Shared Instance Type",
+ "shared_instance_type_default": "Platform default",
+ "shared_instance_type_description": "Use TCP when domain sockets are unavailable (common on Android). Leave default otherwise.",
+ "instance_name": "Instance Name",
+ "instance_name_description": "Isolate multiple shared instances on one system. Default is usually fine.",
+ "rpc_config": "RPC Access",
+ "rpc_config_description": "Copy these lines into another program's [reticulum] section for full shared-instance RPC access (management, interface status, path info).",
+ "rpc_config_unavailable": "RPC key is not available yet. Start Reticulum as a shared instance first.",
+ "rpc_config_copied": "RPC config copied to clipboard",
+ "copy_rpc_config": "Copy RPC Config",
+ "connected_to_shared_instance": "This MeshChatX process is attached to an external shared Reticulum instance. Edit that instance's config to change share/RPC settings.",
+ "copy_failed": "Failed to copy to clipboard"
},
"common": {
"open": "Abierto",
@@ -1599,7 +1619,8 @@
"privacy_desc": "Data, access, and security",
"maintenance": "Maintenance",
"maintenance_desc": "Cleanup, export, and import"
- }
+ },
+ "failed_update_reticulum_instance": "Failed to update Reticulum instance settings!"
},
"debug": {
"title": "Debug Logs",

diff --git a/meshchatx/src/frontend/locales/fi.json b/meshchatx/src/frontend/locales/fi.json
index d040b146..105bb605 100644
--- a/meshchatx/src/frontend/locales/fi.json
+++ b/meshchatx/src/frontend/locales/fi.json
@@ -431,7 +431,27 @@
"desktop_close_behavior_ask": "Ask every time",
"desktop_close_behavior_quit": "Quit application",
"desktop_close_behavior_background": "Keep running in background",
- "desktop_close_behavior_background_no_tray": "Minimize to taskbar"
+ "desktop_close_behavior_background_no_tray": "Minimize to taskbar",
+ "share_reticulum_instance": "Share Reticulum Instance",
+ "share_reticulum_instance_description": "Make this MeshChatX Reticulum instance available to other local programs (Sideband-style shared instance). Other apps can use connectivity without Transport enabled.",
+ "obfuscate_hops": "Obfuscate Hops",
+ "obfuscate_hops_description": "When enabled, Reticulum applies a random local hop delta (RNS local_hops_delta) so outbound traffic does not advertise a true zero-hop origin to the wider mesh.",
+ "respond_to_probes": "Respond to Probes",
+ "respond_to_probes_description": "Allow this instance to answer Reticulum probe requests from the network.",
+ "enable_remote_management": "Enable Remote Management",
+ "enable_remote_management_description": "Allow remote management of this Reticulum instance when configured in the RNS config.",
+ "shared_instance_type": "Shared Instance Type",
+ "shared_instance_type_default": "Platform default",
+ "shared_instance_type_description": "Use TCP when domain sockets are unavailable (common on Android). Leave default otherwise.",
+ "instance_name": "Instance Name",
+ "instance_name_description": "Isolate multiple shared instances on one system. Default is usually fine.",
+ "rpc_config": "RPC Access",
+ "rpc_config_description": "Copy these lines into another program's [reticulum] section for full shared-instance RPC access (management, interface status, path info).",
+ "rpc_config_unavailable": "RPC key is not available yet. Start Reticulum as a shared instance first.",
+ "rpc_config_copied": "RPC config copied to clipboard",
+ "copy_rpc_config": "Copy RPC Config",
+ "connected_to_shared_instance": "This MeshChatX process is attached to an external shared Reticulum instance. Edit that instance's config to change share/RPC settings.",
+ "copy_failed": "Failed to copy to clipboard"
},
"common": {
"open": "Avaa",
@@ -1599,7 +1619,8 @@
"micron_wasm_update_err_activate_failed": "Asennetun tiedoston lataus epäonnistui (tarkista wasm_exec-yhteensopivuus tai kokeile toista versiota). Ohitus poistettiin.",
"micron_wasm_update_toast_installed": "Asennettiin Micron WASM {tag}.",
"micron_wasm_update_toast_uploaded": "Asennettiin WASM tiedostosta.",
- "micron_wasm_update_toast_reverted": "Palautettiin paketin Micron WASM."
+ "micron_wasm_update_toast_reverted": "Palautettiin paketin Micron WASM.",
+ "failed_update_reticulum_instance": "Failed to update Reticulum instance settings!"
},
"debug": {
"title": "Vianetsintälokit",

diff --git a/meshchatx/src/frontend/locales/fr.json b/meshchatx/src/frontend/locales/fr.json
index 37bf5652..b12a0de6 100644
--- a/meshchatx/src/frontend/locales/fr.json
+++ b/meshchatx/src/frontend/locales/fr.json
@@ -431,7 +431,27 @@
"desktop_close_behavior_ask": "Ask every time",
"desktop_close_behavior_quit": "Quit application",
"desktop_close_behavior_background": "Keep running in background",
- "desktop_close_behavior_background_no_tray": "Minimize to taskbar"
+ "desktop_close_behavior_background_no_tray": "Minimize to taskbar",
+ "share_reticulum_instance": "Share Reticulum Instance",
+ "share_reticulum_instance_description": "Make this MeshChatX Reticulum instance available to other local programs (Sideband-style shared instance). Other apps can use connectivity without Transport enabled.",
+ "obfuscate_hops": "Obfuscate Hops",
+ "obfuscate_hops_description": "When enabled, Reticulum applies a random local hop delta (RNS local_hops_delta) so outbound traffic does not advertise a true zero-hop origin to the wider mesh.",
+ "respond_to_probes": "Respond to Probes",
+ "respond_to_probes_description": "Allow this instance to answer Reticulum probe requests from the network.",
+ "enable_remote_management": "Enable Remote Management",
+ "enable_remote_management_description": "Allow remote management of this Reticulum instance when configured in the RNS config.",
+ "shared_instance_type": "Shared Instance Type",
+ "shared_instance_type_default": "Platform default",
+ "shared_instance_type_description": "Use TCP when domain sockets are unavailable (common on Android). Leave default otherwise.",
+ "instance_name": "Instance Name",
+ "instance_name_description": "Isolate multiple shared instances on one system. Default is usually fine.",
+ "rpc_config": "RPC Access",
+ "rpc_config_description": "Copy these lines into another program's [reticulum] section for full shared-instance RPC access (management, interface status, path info).",
+ "rpc_config_unavailable": "RPC key is not available yet. Start Reticulum as a shared instance first.",
+ "rpc_config_copied": "RPC config copied to clipboard",
+ "copy_rpc_config": "Copy RPC Config",
+ "connected_to_shared_instance": "This MeshChatX process is attached to an external shared Reticulum instance. Edit that instance's config to change share/RPC settings.",
+ "copy_failed": "Failed to copy to clipboard"
},
"common": {
"open": "Ouvrir",
@@ -1599,7 +1619,8 @@
"privacy_desc": "Data, access, and security",
"maintenance": "Maintenance",
"maintenance_desc": "Cleanup, export, and import"
- }
+ },
+ "failed_update_reticulum_instance": "Failed to update Reticulum instance settings!"
},
"debug": {
"title": "Débogues",

diff --git a/meshchatx/src/frontend/locales/it.json b/meshchatx/src/frontend/locales/it.json
index 3fb39e62..d60c1a25 100644
--- a/meshchatx/src/frontend/locales/it.json
+++ b/meshchatx/src/frontend/locales/it.json
@@ -431,7 +431,27 @@
"desktop_close_behavior_ask": "Ask every time",
"desktop_close_behavior_quit": "Quit application",
"desktop_close_behavior_background": "Keep running in background",
- "desktop_close_behavior_background_no_tray": "Minimize to taskbar"
+ "desktop_close_behavior_background_no_tray": "Minimize to taskbar",
+ "share_reticulum_instance": "Share Reticulum Instance",
+ "share_reticulum_instance_description": "Make this MeshChatX Reticulum instance available to other local programs (Sideband-style shared instance). Other apps can use connectivity without Transport enabled.",
+ "obfuscate_hops": "Obfuscate Hops",
+ "obfuscate_hops_description": "When enabled, Reticulum applies a random local hop delta (RNS local_hops_delta) so outbound traffic does not advertise a true zero-hop origin to the wider mesh.",
+ "respond_to_probes": "Respond to Probes",
+ "respond_to_probes_description": "Allow this instance to answer Reticulum probe requests from the network.",
+ "enable_remote_management": "Enable Remote Management",
+ "enable_remote_management_description": "Allow remote management of this Reticulum instance when configured in the RNS config.",
+ "shared_instance_type": "Shared Instance Type",
+ "shared_instance_type_default": "Platform default",
+ "shared_instance_type_description": "Use TCP when domain sockets are unavailable (common on Android). Leave default otherwise.",
+ "instance_name": "Instance Name",
+ "instance_name_description": "Isolate multiple shared instances on one system. Default is usually fine.",
+ "rpc_config": "RPC Access",
+ "rpc_config_description": "Copy these lines into another program's [reticulum] section for full shared-instance RPC access (management, interface status, path info).",
+ "rpc_config_unavailable": "RPC key is not available yet. Start Reticulum as a shared instance first.",
+ "rpc_config_copied": "RPC config copied to clipboard",
+ "copy_rpc_config": "Copy RPC Config",
+ "connected_to_shared_instance": "This MeshChatX process is attached to an external shared Reticulum instance. Edit that instance's config to change share/RPC settings.",
+ "copy_failed": "Failed to copy to clipboard"
},
"common": {
"open": "Apri",
@@ -1651,7 +1671,8 @@
"privacy_desc": "Data, access, and security",
"maintenance": "Maintenance",
"maintenance_desc": "Cleanup, export, and import"
- }
+ },
+ "failed_update_reticulum_instance": "Failed to update Reticulum instance settings!"
},
"debug": {
"title": "Log di Debug",

diff --git a/meshchatx/src/frontend/locales/nl.json b/meshchatx/src/frontend/locales/nl.json
index aa773deb..9eca8cb3 100644
--- a/meshchatx/src/frontend/locales/nl.json
+++ b/meshchatx/src/frontend/locales/nl.json
@@ -431,7 +431,27 @@
"desktop_close_behavior_ask": "Ask every time",
"desktop_close_behavior_quit": "Quit application",
"desktop_close_behavior_background": "Keep running in background",
- "desktop_close_behavior_background_no_tray": "Minimize to taskbar"
+ "desktop_close_behavior_background_no_tray": "Minimize to taskbar",
+ "share_reticulum_instance": "Share Reticulum Instance",
+ "share_reticulum_instance_description": "Make this MeshChatX Reticulum instance available to other local programs (Sideband-style shared instance). Other apps can use connectivity without Transport enabled.",
+ "obfuscate_hops": "Obfuscate Hops",
+ "obfuscate_hops_description": "When enabled, Reticulum applies a random local hop delta (RNS local_hops_delta) so outbound traffic does not advertise a true zero-hop origin to the wider mesh.",
+ "respond_to_probes": "Respond to Probes",
+ "respond_to_probes_description": "Allow this instance to answer Reticulum probe requests from the network.",
+ "enable_remote_management": "Enable Remote Management",
+ "enable_remote_management_description": "Allow remote management of this Reticulum instance when configured in the RNS config.",
+ "shared_instance_type": "Shared Instance Type",
+ "shared_instance_type_default": "Platform default",
+ "shared_instance_type_description": "Use TCP when domain sockets are unavailable (common on Android). Leave default otherwise.",
+ "instance_name": "Instance Name",
+ "instance_name_description": "Isolate multiple shared instances on one system. Default is usually fine.",
+ "rpc_config": "RPC Access",
+ "rpc_config_description": "Copy these lines into another program's [reticulum] section for full shared-instance RPC access (management, interface status, path info).",
+ "rpc_config_unavailable": "RPC key is not available yet. Start Reticulum as a shared instance first.",
+ "rpc_config_copied": "RPC config copied to clipboard",
+ "copy_rpc_config": "Copy RPC Config",
+ "connected_to_shared_instance": "This MeshChatX process is attached to an external shared Reticulum instance. Edit that instance's config to change share/RPC settings.",
+ "copy_failed": "Failed to copy to clipboard"
},
"common": {
"open": "Open",
@@ -1599,7 +1619,8 @@
"privacy_desc": "Data, access, and security",
"maintenance": "Maintenance",
"maintenance_desc": "Cleanup, export, and import"
- }
+ },
+ "failed_update_reticulum_instance": "Failed to update Reticulum instance settings!"
},
"debug": {
"title": "Debuglogs",

diff --git a/meshchatx/src/frontend/locales/ru.json b/meshchatx/src/frontend/locales/ru.json
index 709e054b..d2510226 100644
--- a/meshchatx/src/frontend/locales/ru.json
+++ b/meshchatx/src/frontend/locales/ru.json
@@ -431,7 +431,27 @@
"desktop_close_behavior_ask": "Ask every time",
"desktop_close_behavior_quit": "Quit application",
"desktop_close_behavior_background": "Keep running in background",
- "desktop_close_behavior_background_no_tray": "Minimize to taskbar"
+ "desktop_close_behavior_background_no_tray": "Minimize to taskbar",
+ "share_reticulum_instance": "Share Reticulum Instance",
+ "share_reticulum_instance_description": "Make this MeshChatX Reticulum instance available to other local programs (Sideband-style shared instance). Other apps can use connectivity without Transport enabled.",
+ "obfuscate_hops": "Obfuscate Hops",
+ "obfuscate_hops_description": "When enabled, Reticulum applies a random local hop delta (RNS local_hops_delta) so outbound traffic does not advertise a true zero-hop origin to the wider mesh.",
+ "respond_to_probes": "Respond to Probes",
+ "respond_to_probes_description": "Allow this instance to answer Reticulum probe requests from the network.",
+ "enable_remote_management": "Enable Remote Management",
+ "enable_remote_management_description": "Allow remote management of this Reticulum instance when configured in the RNS config.",
+ "shared_instance_type": "Shared Instance Type",
+ "shared_instance_type_default": "Platform default",
+ "shared_instance_type_description": "Use TCP when domain sockets are unavailable (common on Android). Leave default otherwise.",
+ "instance_name": "Instance Name",
+ "instance_name_description": "Isolate multiple shared instances on one system. Default is usually fine.",
+ "rpc_config": "RPC Access",
+ "rpc_config_description": "Copy these lines into another program's [reticulum] section for full shared-instance RPC access (management, interface status, path info).",
+ "rpc_config_unavailable": "RPC key is not available yet. Start Reticulum as a shared instance first.",
+ "rpc_config_copied": "RPC config copied to clipboard",
+ "copy_rpc_config": "Copy RPC Config",
+ "connected_to_shared_instance": "This MeshChatX process is attached to an external shared Reticulum instance. Edit that instance's config to change share/RPC settings.",
+ "copy_failed": "Failed to copy to clipboard"
},
"common": {
"open": "Открыть",
@@ -2976,7 +2996,8 @@
"privacy_desc": "Data, access, and security",
"maintenance": "Maintenance",
"maintenance_desc": "Cleanup, export, and import"
- }
+ },
+ "failed_update_reticulum_instance": "Failed to update Reticulum instance settings!"
},
"debug": {
"title": "Журнал отладки",

diff --git a/meshchatx/src/frontend/locales/zh.json b/meshchatx/src/frontend/locales/zh.json
index 00f517b3..c1f894dc 100644
--- a/meshchatx/src/frontend/locales/zh.json
+++ b/meshchatx/src/frontend/locales/zh.json
@@ -431,7 +431,27 @@
"desktop_close_behavior_ask": "Ask every time",
"desktop_close_behavior_quit": "Quit application",
"desktop_close_behavior_background": "Keep running in background",
- "desktop_close_behavior_background_no_tray": "Minimize to taskbar"
+ "desktop_close_behavior_background_no_tray": "Minimize to taskbar",
+ "share_reticulum_instance": "Share Reticulum Instance",
+ "share_reticulum_instance_description": "Make this MeshChatX Reticulum instance available to other local programs (Sideband-style shared instance). Other apps can use connectivity without Transport enabled.",
+ "obfuscate_hops": "Obfuscate Hops",
+ "obfuscate_hops_description": "When enabled, Reticulum applies a random local hop delta (RNS local_hops_delta) so outbound traffic does not advertise a true zero-hop origin to the wider mesh.",
+ "respond_to_probes": "Respond to Probes",
+ "respond_to_probes_description": "Allow this instance to answer Reticulum probe requests from the network.",
+ "enable_remote_management": "Enable Remote Management",
+ "enable_remote_management_description": "Allow remote management of this Reticulum instance when configured in the RNS config.",
+ "shared_instance_type": "Shared Instance Type",
+ "shared_instance_type_default": "Platform default",
+ "shared_instance_type_description": "Use TCP when domain sockets are unavailable (common on Android). Leave default otherwise.",
+ "instance_name": "Instance Name",
+ "instance_name_description": "Isolate multiple shared instances on one system. Default is usually fine.",
+ "rpc_config": "RPC Access",
+ "rpc_config_description": "Copy these lines into another program's [reticulum] section for full shared-instance RPC access (management, interface status, path info).",
+ "rpc_config_unavailable": "RPC key is not available yet. Start Reticulum as a shared instance first.",
+ "rpc_config_copied": "RPC config copied to clipboard",
+ "copy_rpc_config": "Copy RPC Config",
+ "connected_to_shared_instance": "This MeshChatX process is attached to an external shared Reticulum instance. Edit that instance's config to change share/RPC settings.",
+ "copy_failed": "Failed to copy to clipboard"
},
"common": {
"open": "打开",
@@ -1599,7 +1619,8 @@
"privacy_desc": "Data, access, and security",
"maintenance": "Maintenance",
"maintenance_desc": "Cleanup, export, and import"
- }
+ },
+ "failed_update_reticulum_instance": "Failed to update Reticulum instance settings!"
},
"debug": {
"title": "调试日志",

diff --git a/tests/backend/fixtures/http_api_routes.json b/tests/backend/fixtures/http_api_routes.json
index 88115374..5089154c 100644
--- a/tests/backend/fixtures/http_api_routes.json
+++ b/tests/backend/fixtures/http_api_routes.json
@@ -828,6 +828,14 @@
"method": "POST",
"path": "/api/v1/reticulum/enable-transport"
},
+ {
+ "method": "GET",
+ "path": "/api/v1/reticulum/instance"
+ },
+ {
+ "method": "PATCH",
+ "path": "/api/v1/reticulum/instance"
+ },
{
"method": "GET",
"path": "/api/v1/reticulum/interfaces"

diff --git a/tests/backend/http_api_response_registry.py b/tests/backend/http_api_response_registry.py
index e6e899f3..7e3499f0 100644
--- a/tests/backend/http_api_response_registry.py
+++ b/tests/backend/http_api_response_registry.py
@@ -42,6 +42,7 @@ from tests.backend.http_api_response_schemas import (
DESTINATION_STAMP_INFO_SCHEMA,
DISCOVERED_INTERFACES_SCHEMA,
DISCOVERY_CONFIG_SCHEMA,
+ RETICULUM_INSTANCE_SCHEMA,
DOCS_SEARCH_SCHEMA,
DOCS_STATUS_SCHEMA,
ERROR_ENVELOPE_SCHEMA,
@@ -141,6 +142,7 @@ HTTP_JSON_GET_CONTRACTS: tuple[HttpJsonContract, ...] = (
"GET", "/api/v1/community-interfaces", COMMUNITY_INTERFACES_SCHEMA
),
HttpJsonContract("GET", "/api/v1/reticulum/discovery", DISCOVERY_CONFIG_SCHEMA),
+ HttpJsonContract("GET", "/api/v1/reticulum/instance", RETICULUM_INSTANCE_SCHEMA),
HttpJsonContract(
"GET", "/api/v1/reticulum/discovered-interfaces", DISCOVERED_INTERFACES_SCHEMA
),

diff --git a/tests/backend/http_api_response_schemas.py b/tests/backend/http_api_response_schemas.py
index a03227a5..e7dfaa61 100644
--- a/tests/backend/http_api_response_schemas.py
+++ b/tests/backend/http_api_response_schemas.py
@@ -206,6 +206,35 @@ DISCOVERY_CONFIG_SCHEMA: dict = {
"additionalProperties": True,
}
+RETICULUM_INSTANCE_SCHEMA: dict = {
+ "type": "object",
+ "required": ["instance"],
+ "properties": {
+ "instance": {
+ "type": "object",
+ "required": [
+ "share_instance",
+ "local_hops_delta",
+ "is_connected_to_shared_instance",
+ ],
+ "properties": {
+ "share_instance": _BOOLEAN,
+ "local_hops_delta": _BOOLEAN,
+ "respond_to_probes": _BOOLEAN,
+ "enable_remote_management": _BOOLEAN,
+ "shared_instance_type": {},
+ "instance_name": {},
+ "rpc_key": {},
+ "rpc_config_snippet": {},
+ "is_connected_to_shared_instance": _BOOLEAN,
+ "enable_transport": _BOOLEAN,
+ },
+ "additionalProperties": True,
+ }
+ },
+ "additionalProperties": True,
+}
+
DISCOVERED_INTERFACES_SCHEMA: dict = {
"type": "object",
"required": ["interfaces"],

diff --git a/tests/backend/test_reticulum_instance_settings.py b/tests/backend/test_reticulum_instance_settings.py
new file mode 100644
index 00000000..8ad3bf5e
--- /dev/null
+++ b/tests/backend/test_reticulum_instance_settings.py
@@ -0,0 +1,179 @@
+# SPDX-License-Identifier: 0BSD
+
+import json
+import shutil
+import tempfile
+from unittest.mock import AsyncMock, MagicMock, patch
+
+import pytest
+import RNS
+
+from meshchatx.meshchat import ReticulumMeshChat
+
+
+class ConfigDict(dict):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.write_called = False
+
+ def write(self):
+ self.write_called = True
+ return True
+
+
+@pytest.fixture
+def temp_dir():
+ path = tempfile.mkdtemp()
+ try:
+ yield path
+ finally:
+ shutil.rmtree(path)
+
+
+def build_identity():
+ identity = MagicMock(spec=RNS.Identity)
+ identity.hash = b"test_hash_32_bytes_long_01234567"
+ identity.hexhash = identity.hash.hex()
+ identity.get_private_key.return_value = b"test_private_key"
+ return identity
+
+
+async def find_route_handler(app_instance, path, method):
+ for route in app_instance.get_routes():
+ if route.path == path and route.method == method:
+ return route.handler
+ return None
+
+
+def test_parse_rns_config_bool():
+ assert ReticulumMeshChat._parse_rns_config_bool("Yes") is True
+ assert ReticulumMeshChat._parse_rns_config_bool("No") is False
+ assert ReticulumMeshChat._parse_rns_config_bool(True) is True
+ assert ReticulumMeshChat._parse_rns_config_bool(None, default=True) is True
+ assert ReticulumMeshChat._format_rns_config_bool(True) == "Yes"
+ assert ReticulumMeshChat._format_rns_config_bool(False) == "No"
+
+
+@pytest.mark.asyncio
+async def test_reticulum_instance_get_and_patch(temp_dir):
+ config = ConfigDict(
+ {
+ "reticulum": {
+ "share_instance": "Yes",
+ "local_hops_delta": "No",
+ "enable_transport": "No",
+ "respond_to_probes": "No",
+ "enable_remote_management": "No",
+ "instance_name": "default",
+ "shared_instance_type": "tcp",
+ "rpc_key": "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899",
+ },
+ "interfaces": {},
+ },
+ )
+
+ with (
+ patch("meshchatx.meshchat.generate_ssl_certificate"),
+ patch("RNS.Reticulum") as mock_rns,
+ patch("RNS.Transport"),
+ patch("LXMF.LXMRouter"),
+ ):
+ mock_reticulum = mock_rns.return_value
+ mock_reticulum.config = config
+ mock_reticulum.configpath = "/tmp/mock_config"
+ mock_reticulum.is_connected_to_shared_instance = False
+ mock_reticulum.share_instance = True
+ mock_reticulum.shared_instance_type = "tcp"
+ mock_reticulum.rpc_key = bytes.fromhex(
+ "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899",
+ )
+ mock_reticulum.transport_enabled.return_value = False
+
+ app_instance = ReticulumMeshChat(
+ identity=build_identity(),
+ storage_dir=temp_dir,
+ reticulum_config_dir=temp_dir,
+ )
+ app_instance.reload_reticulum = AsyncMock(return_value=True)
+
+ get_handler = await find_route_handler(
+ app_instance,
+ "/api/v1/reticulum/instance",
+ "GET",
+ )
+ patch_handler = await find_route_handler(
+ app_instance,
+ "/api/v1/reticulum/instance",
+ "PATCH",
+ )
+ assert get_handler and patch_handler
+
+ get_response = await get_handler(MagicMock())
+ get_data = json.loads(get_response.body)
+ assert get_data["instance"]["share_instance"] is True
+ assert get_data["instance"]["local_hops_delta"] is False
+ assert get_data["instance"]["shared_instance_type"] == "tcp"
+ assert get_data["instance"]["rpc_config_snippet"]
+ assert "rpc_key =" in get_data["instance"]["rpc_config_snippet"]
+
+ class PatchRequest:
+ @staticmethod
+ async def json():
+ return {
+ "share_instance": True,
+ "local_hops_delta": True,
+ "respond_to_probes": True,
+ "shared_instance_type": "unix",
+ "instance_name": "meshchatx",
+ }
+
+ patch_response = await patch_handler(PatchRequest())
+ patch_data = json.loads(patch_response.body)
+ assert patch_response.status == 200
+ assert patch_data["instance"]["local_hops_delta"] is True
+ assert patch_data["instance"]["respond_to_probes"] is True
+ assert patch_data["instance"]["shared_instance_type"] == "unix"
+ assert patch_data["instance"]["instance_name"] == "meshchatx"
+ assert config["reticulum"]["local_hops_delta"] == "Yes"
+ assert config["reticulum"]["shared_instance_type"] == "unix"
+ assert config.write_called is True
+ app_instance.reload_reticulum.assert_awaited_once()
+
+
+@pytest.mark.asyncio
+async def test_reticulum_instance_rejects_bad_type(temp_dir):
+ config = ConfigDict({"reticulum": {"share_instance": "Yes"}, "interfaces": {}})
+
+ with (
+ patch("meshchatx.meshchat.generate_ssl_certificate"),
+ patch("RNS.Reticulum") as mock_rns,
+ patch("RNS.Transport"),
+ patch("LXMF.LXMRouter"),
+ ):
+ mock_reticulum = mock_rns.return_value
+ mock_reticulum.config = config
+ mock_reticulum.configpath = "/tmp/mock_config"
+ mock_reticulum.is_connected_to_shared_instance = False
+ mock_reticulum.share_instance = True
+ mock_reticulum.transport_enabled.return_value = False
+
+ app_instance = ReticulumMeshChat(
+ identity=build_identity(),
+ storage_dir=temp_dir,
+ reticulum_config_dir=temp_dir,
+ )
+ app_instance.reload_reticulum = AsyncMock(return_value=True)
+
+ patch_handler = await find_route_handler(
+ app_instance,
+ "/api/v1/reticulum/instance",
+ "PATCH",
+ )
+
+ class PatchRequest:
+ @staticmethod
+ async def json():
+ return {"shared_instance_type": "udp"}
+
+ response = await patch_handler(PatchRequest())
+ assert response.status == 400

diff --git a/tests/frontend/behaviorContracts.test.js b/tests/frontend/behaviorContracts.test.js
index c1ab4059..4ae225ea 100644
--- a/tests/frontend/behaviorContracts.test.js
+++ b/tests/frontend/behaviorContracts.test.js
@@ -154,10 +154,12 @@ describe("behavior contracts: dead API surface", () => {
describe("behavior contracts: Android Chaquopy Python sync", () => {
it("Gradle syncs vendored lxmfy into Chaquopy python sources", () => {
const gradle = readSource("android/app/build.gradle");
- expect(gradle).toContain('vendor/lxmfy/lxmfy');
- expect(gradle).toContain('syncLxmfyPython');
- expect(gradle).toContain('src/main/python/lxmfy');
- expect(gradle).toMatch(/dependsOn\(tasks\.named\("syncMeshchatPython"\),\s*tasks\.named\("syncLxmfyPython"\)\)/);
+ expect(gradle).toContain("vendor/lxmfy/lxmfy");
+ expect(gradle).toContain("syncLxmfyPython");
+ expect(gradle).toContain("src/main/python/lxmfy");
+ expect(gradle).toMatch(
+ /dependsOn\(tasks\.named\("syncMeshchatPython"\),\s*tasks\.named\("syncLxmfyPython"\)\)/
+ );
const initPy = readSource("vendor/lxmfy/lxmfy/__init__.py");
expect(initPy.length).toBeGreaterThan(0);
});

diff --git a/tests/frontend/settingsReticulumInstanceService.test.js b/tests/frontend/settingsReticulumInstanceService.test.js
new file mode 100644
index 00000000..e5935392
--- /dev/null
+++ b/tests/frontend/settingsReticulumInstanceService.test.js
@@ -0,0 +1,36 @@
+import { describe, it, expect, vi } from "vitest";
+import {
+ applyReticulumInstanceSettings,
+ fetchReticulumInstanceSettings,
+} from "@/js/settings/settingsReticulumInstanceService.js";
+
+describe("settingsReticulumInstanceService", () => {
+ it("fetches instance settings from the API", async () => {
+ const api = {
+ get: vi.fn().mockResolvedValue({
+ data: {
+ instance: {
+ share_instance: true,
+ local_hops_delta: true,
+ },
+ },
+ }),
+ };
+ const instance = await fetchReticulumInstanceSettings(api);
+ expect(api.get).toHaveBeenCalledWith("/api/v1/reticulum/instance");
+ expect(instance.local_hops_delta).toBe(true);
+ });
+
+ it("patches instance settings through the API", async () => {
+ const api = {
+ patch: vi.fn().mockResolvedValue({
+ data: { message: "ok", instance: { local_hops_delta: true } },
+ }),
+ };
+ const response = await applyReticulumInstanceSettings({ local_hops_delta: true }, api);
+ expect(api.patch).toHaveBeenCalledWith("/api/v1/reticulum/instance", {
+ local_hops_delta: true,
+ });
+ expect(response.data.instance.local_hops_delta).toBe(true);
+ });
+});


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────